/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
aLONG with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include "commonheaders.h"

/*
============================================================================================
	the functions
============================================================================================
*/

/**
 * name:	IsUSASCII
 * desc:	determine whether the pBuffer string is ascii or not
 * param:	pBuffer	- string to check
 *
 * return	TRUE or FALSE
 **/
BOOLEAN IsUSASCII(LPCSTR pBuffer, LPDWORD pcbBuffer) 
{
	BYTE c;
	PBYTE s = (PBYTE)pBuffer;
	BOOLEAN bIsUTF = 0;

	if(s == NULL) return 1;
	while((c = *s++) != 0) {
		if(c < 0x80) continue;
		if(!pcbBuffer) return 0;
		bIsUTF = 1;
	}
	if(pcbBuffer) *pcbBuffer = s - (PBYTE)pBuffer;
	return !bIsUTF;
}

/**
 * name:	IsUnicodeASCII
 * desc:	determine whether the pBuffer string is ascii or not
 * param:	pBuffer	- string to check
 *			cbSize	- size of the string
 *
 * return	TRUE or FALSE
 **/
BOOLEAN IsUnicodeASCII(LPCWSTR pBuffer, DWORD cchSize) 
{
	while(cchSize > 0 && *pBuffer <= 0x7F) {
		cchSize--;
		pBuffer++;
	}
	return *pBuffer <= 0x7F;
}

/**
 * name:	IsUtf8Valid
 * desc:	check if an utf8 string is valid
 * param:	utfIn	- string to check
 *
 * return	TRUE or FALSE
 **/
BOOLEAN IsUtf8Valid(LPCSTR utfIn)
{
	DWORD nb, i;
	PBYTE s = (PBYTE)utfIn;
	BYTE c;

	while((c = *s) != 0) {
		if(!(c & 0x80))	nb = 0;
		else if((c & 0xC0) == 0x80) return FALSE;
		else if((c & 0xE0) == 0xC0) nb = 1;
		else if((c & 0xF0) == 0xE0) nb = 2;
		else if((c & 0xF8) == 0xF0) nb = 3;
		else if((c & 0xFC) == 0xF8) nb = 4;
		else if((c & 0xFE) == 0xFC) nb = 5;

		for(i = 1; i <= nb; i++) // we this forward, do not cross end of string
			if((s[i] & 0xC0) != 0x80)
				return FALSE;
		s += nb + 1;
	}
	return TRUE;
}

/**
 * name:	MultiByteToUtf8Length
 * desc:	calculate the number of bytes an utf8 encoded string with the content of pwszIn would have
 * param:	pwszIn	- input string
 *
 * return:	number of bytes, a utf8 encoded string needs excluding the 0 termination
 **/
DWORD MultiByteToUtf8Length(LPCSTR pszIn)
{
	return (pszIn == NULL) ? 0 : 3 * mir_strlen(pszIn);
}

/**
 * name:	MultiByteToUtf8Static
 * desc:	encodes an ansi string to utf 8
 *			It costs only about 25% of the time WideCharToMultibyte(CP_UTF8...) needs for translation!
 *			Use carefully! output is simply truncated if the <pwszOut> string is not large enough!
 * param:	utfIn	- string to convert
 *
 * return	number of bytes, written to the <utfOut>
 **/
DWORD MultiByteToUtf8Static(LPCSTR pszIn, LPSTR utfOut, DWORD cchOut)
{
	DWORD dwLength;
	LPWSTR pwszOut;

	if(IsUSASCII(pszIn, &dwLength)) {
		dwLength = min(cchOut - 1, dwLength);
		memcpy(utfOut, pszIn, dwLength);
		utfOut[dwLength] = 0;
		return dwLength;
	}

	if(!(pwszOut = (LPWSTR)_alloca((cchOut + 1) * sizeof(WCHAR))))
		return 0;
	if(FAILED(MultiByteToWideChar(myGlobals.CodePage, 0, pszIn, -1, pwszOut, cchOut)))
		return 0;
	pwszOut[cchOut] = 0;
	return WideCharToUtf8Static(pwszOut, utfOut, cchOut);
}

/**
 * name:	WideCharToUtf8
 * desc:	Calculates and allocates the requiered memory and encodes an unicode string to utf 8
 *			It costs only about 25% of the time WideCharToMultibyte(CP_UTF8...) needs for translation!
 *			
 * param:	utfIn	- string to convert
 *
 * return	pointer to the allocated utf8 string
 **/
LPSTR MultiByteToUtf8(LPCSTR pszIn, LPDWORD pcchUtf8)
{
	DWORD dwLength;
	LPWSTR pwszIn;

	// convert multibyte to widechar
	if((dwLength = MultiByteToWideChar(myGlobals.CodePage, 0, pszIn, -1, NULL, NULL)) <= 0)
		return NULL;
	if(!(pwszIn = (LPWSTR)_alloca((dwLength + 1) * sizeof(WCHAR))))
		return NULL;
	if(FAILED(MultiByteToWideChar(myGlobals.CodePage, 0, pszIn, -1, pwszIn, dwLength)))
		return NULL;
	pwszIn[dwLength] = 0;
	// convert the widechar to utf8
	return WideCharToUtf8(pwszIn, pcchUtf8);
}

/**
 * name:	WideCharToUtf8Length
 * desc:	calculate the number of bytes an utf8 encoded string with the content of pwszIn would have
 * param:	pwszIn	- input string
 *
 * return:	number of bytes, a utf8 encoded string needs excluding the 0 termination
 **/
DWORD WideCharToUtf8Length(LPCWSTR pwszIn)
{
	return pwszIn == NULL ? 0 : wcslen(pwszIn);
}

/**
 * name:	WideCharToUtf8Static
 * desc:	encodes an unicode string to utf 8
 *			It costs only about 25% of the time WideCharToMultibyte(CP_UTF8...) needs for translation!
 *			Use carefully! output is simply truncated if the <pwszOut> string is not large enough!
 * param:	utfIn	- string to convert
 *
 * return	number of bytes, written to the <pwszOut>
 **/
DWORD WideCharToUtf8Static(LPCWSTR pwszIn, LPSTR utfOut, DWORD cchOut)
{
	PBYTE d;
	WORD  U;

	if(!pwszIn || !utfOut)
		return 0;

	d = (PBYTE)utfOut;
	while(*pwszIn && --cchOut > 0) {
		U = *pwszIn++;

		if(U < 0x80) {
			*d++ = U & 0x00FF;
			continue;
		}
		if (U < 0x800) {
			*d++ = 0xC0 + ((U >> 6) & 0x3F);
			*d++ = 0x80 + (U & 0x003F);
			continue;
		}
		*d++ = 0xE0 + (U >> 12);
		*d++ = 0x80 + ((U >> 6) & 0x3F);
		*d++ = 0x80 + (U & 0x3F);
	}
	*d = 0;
	return (DWORD)(d - (PBYTE)utfOut);
}

/**
 * name:	WideCharToUtf8
 * desc:	Calculates and allocates the requiered memory and encodes an unicode string to utf 8
 *			It costs only about 25% of the time WideCharToMultibyte(CP_UTF8...) needs for translation!
 *			
 * param:	utfIn	- string to convert
 *
 * return	pointer to the allocated utf8 string
 **/
LPSTR WideCharToUtf8(LPCWSTR pwszIn, LPDWORD pcchUtf8)
{
	LPSTR	utfOut;
	DWORD	size;
	
	if(pwszIn == NULL)
		return NULL;

	if(pcchUtf8) *pcchUtf8 = 0;
	size = wcslen(pwszIn);
	if(size == 0) return NULL;
	size = size * 3 + 1;
	if(!(utfOut = (LPSTR)mir_alloc(size  * sizeof(BYTE))))
		return NULL;
	if((size = WideCharToUtf8Static(pwszIn, utfOut, size)) == 0) {
		mir_free(utfOut);
		return NULL;
	}
	if(pcchUtf8) 
		*pcchUtf8 = size;
	return utfOut;
}

/**
 * name:	Utf8DecodedLength
 * desc:	calculate the number of bytes an unicode string with the content of utfIn
 *			would have without the terminating null character
 * param:	utfIn	- input string
 *
 * return:	number of bytes, a unicode string needs excluding the 0 termination
 **/
DWORD Utf8DecodedLength(LPCSTR utfIn)
{
	DWORD cchOut = 0;
	PBYTE s = (PBYTE)utfIn;
	BYTE  c;

	if(s == 0) return 0;
	
	while((c = *s) != 0 && cchOut < ULONG_MAX) {
		if((c & 0x80) == 0) s += 1;
		else if ((c & 0xE0) == 0xE0) s += 3;
		else s += 2;
		cchOut += 1;
	}
	return (cchOut);
}

/**
 * name:	Utf8ToWideCharStatic
 * desc:	decodes an utf8 string to unicode
 *			It costs only about 25% of the time WideCharToMultibyte(CP_UTF8...) needs for translation!
 *			Use carefully! output is simply truncated if the <pwszOut> string is not large enough!
 * param:	utfIn	- string to convert
 *			pwszOut	- pointer to a buffer that retrieves the unicode string
 *			cchOut	- maximum number of characters including the terminating null character, the buffer can take 
 *
 * return	number of bytes, written to the <pwszOut>
 **/
DWORD Utf8ToWideCharStatic(LPCSTR utfIn, LPWSTR pwszOut, DWORD cchOut)
{
	LPWSTR d = pwszOut;
	PBYTE  s = (PBYTE)utfIn;

	if(pwszOut == NULL)
		return 0; 

	while(*s && --cchOut > 0) {
		if (( *s & 0x80 ) == 0 ) {
			*d++ = *s++;
			continue;
		}

		if (( s[0] & 0xE0 ) == 0xE0 && ( s[1] & 0xC0 ) == 0x80 && ( s[2] & 0xC0 ) == 0x80 ) {
			*d++ = (( WORD )( s[0] & 0x0F) << 12 ) + ( WORD )(( s[1] & 0x3F ) << 6 ) + ( WORD )( s[2] & 0x3F );
			s += 3;
			continue;
		}

		if (( s[0] & 0xE0 ) == 0xC0 && ( s[1] & 0xC0 ) == 0x80 ) {
			*d++ = ( WORD )(( s[0] & 0x1F ) << 6 ) + ( WORD )( s[1] & 0x3F );
			s += 2;
			continue;
		}

		*d++ = *s++;
	}
	*d = 0;

	return (DWORD)(d - pwszOut);
}


/**
 * name:	Utf8ToWideChar
 * desc:	Calculates and allocates the requiered memory and decodes an utf8 string to unicode
 *			It costs only about 25% of the time WideCharToMultibyte(CP_UTF8...) needs for translation!
 *			
 * param:	utfIn	- string to convert
 *			pcchOut	- pointer to a dword that retrieves the length of the resulting unicode string
 *
 * return	pointer to the allocated unicode string
 **/
LPWSTR Utf8ToWideChar(LPCSTR utfIn, LPDWORD pcchOut)
{
	LPWSTR	pwszOut;
	DWORD	cchOut = Utf8DecodedLength(utfIn);

	if(pcchOut) *pcchOut = 0;
	if(cchOut == 0) return NULL;
	if(!(pwszOut = (LPWSTR)mir_alloc((cchOut + 1) * sizeof(WCHAR))))
		return NULL;
	if((cchOut = Utf8ToWideCharStatic(utfIn, pwszOut, cchOut + 1)) == 0) {
		mir_free(pwszOut);
		return NULL;
	}
	if(pcchOut) *pcchOut = cchOut;
	return pwszOut;
}

/**
 * name:	Utf8ToMultiByteStatic
 * desc:	decodes an utf8 string to ansi
 *			
 * param:	utfIn	- string to convert
 *			pszOut	- pointer to a buffer that retrieves the multibyte string
 *			cchOut	- maximum number of characters including the terminating null character, the buffer can take 
 * return	pointer to the allocated unicode string
 **/
DWORD Utf8ToMultiByteStatic(LPCSTR utfIn, LPSTR pszOut, DWORD cchOut)
{
	LPWSTR pwszOut;
	DWORD dwLength;

	// if utfIn is a ascii text, just copy it
	if(IsUSASCII(utfIn, &dwLength)) {
		dwLength = min(dwLength, cchOut - 1);
		memcpy(pszOut, utfIn, dwLength);
		pszOut[dwLength] = 0;
		return dwLength;
	}

	if(!(pwszOut = (LPWSTR)_alloca(cchOut * sizeof(WCHAR)))) {
		*pszOut = 0;
		return 0;
	}
	// check if string is truncated
	if((dwLength = Utf8ToWideCharStatic(utfIn, pwszOut, cchOut)) > 0) {
		if(FAILED(WideCharToMultiByte(myGlobals.CodePage, 0, pwszOut, -1, pszOut, cchOut, NULL, NULL))) {
			*pszOut = 0;
			return 0;
		}
	}
	return dwLength;
}

/**
 * name:	Utf8ToMultiByte
 * desc:	decodes an utf8 string to ansi
 *			
 * param:	utfIn	- string to convert
 *			pcchOut	- pointer to a dword that retrieves the length of the resulting multibyte string
 *
 * return	pointer to the allocated unicode string
 **/
LPSTR Utf8ToMultiByte(LPCSTR utfIn, LPDWORD pcchOut)
{
	LPSTR pszOut;
	LPWSTR pwszOut;
	DWORD dwLength = 0;

	if(pcchOut) *pcchOut = 0;

	// convert utf to widechar
	if(!(dwLength = Utf8DecodedLength(utfIn)))
		return NULL;
	if(!(pwszOut = (LPWSTR)alloca((dwLength + 1) * sizeof(WCHAR))))
		return NULL;
	if(dwLength != Utf8ToWideCharStatic(utfIn, pwszOut, dwLength + 1))
		return NULL;

	// convert widechar to multibyte
	dwLength = WideCharToMultiByte(myGlobals.CodePage, 0, pwszOut, -1, NULL, NULL, NULL, NULL);
	if(!(pszOut = (LPSTR)mir_alloc((dwLength + 1) * sizeof(CHAR))))
		return NULL;
	if(FAILED(WideCharToMultiByte(myGlobals.CodePage, 0, pwszOut, -1, pszOut, dwLength, NULL, NULL))) {
		mir_free(pszOut);
		return NULL;
	}
	pszOut[dwLength] = 0;
	if(pcchOut) *pcchOut = dwLength;
	return pszOut;
}


LPSTR WideCharToMultiByte(LPCWSTR pwszIn)
{
	if(pwszIn) {
		LPSTR pszOut;
		INT length = WideCharToMultiByte(myGlobals.CodePage, 0, pwszIn, INT_MAX, NULL, NULL, NULL, NULL);

		if(length > 0 && (pszOut = (LPSTR)mir_alloc(length + 1))) {
			if(WideCharToMultiByte(myGlobals.CodePage, 0, pwszIn, INT_MAX, pszOut, length + 1, NULL, NULL) > 0)
				return pszOut;
			mir_free(pszOut);
		}
	}
	return NULL;
}

LPWSTR MultiByteToWideChar(LPCSTR pszIn)
{
	if(pszIn) {
		LPWSTR pwszOut;
		INT length = MultiByteToWideChar(myGlobals.CodePage, 0, pszIn, INT_MAX, NULL, NULL);

		if(length > 0 && (pwszOut = (LPWSTR)mir_alloc(length + 1))) {
			if(MultiByteToWideChar(myGlobals.CodePage, 0, pszIn, INT_MAX, pwszOut, length + 1) > 0)
				return pwszOut;
			mir_free(pwszOut);
		}
	}
	return NULL;
}